package cn.edu.zzuli.nothinglink.service.impl;

import cn.edu.zzuli.nothinglink.constant.LiveConst;
import cn.edu.zzuli.nothinglink.constant.ResCodeConst;
import cn.edu.zzuli.nothinglink.entity.*;
import cn.edu.zzuli.nothinglink.mapper.LiveRecordMapper;
import cn.edu.zzuli.nothinglink.mapper.SignMapper;
import cn.edu.zzuli.nothinglink.mapper.StudentsMapper;
import cn.edu.zzuli.nothinglink.service.ClassesService;
import cn.edu.zzuli.nothinglink.service.SignService;
import cn.edu.zzuli.nothinglink.vo.SignClassInfoVo;
import cn.edu.zzuli.nothinglink.vo.SignInfoVo;
import cn.edu.zzuli.nothinglink.vo.SignRecordVo;
import cn.edu.zzuli.nothinglink.ws.LiveLinkEndPoint;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author suzyyy
 * @since 2021-04-26
 */
@Service
public class SignServiceImpl extends ServiceImpl<SignMapper, Sign> implements SignService {

    @Resource
    SignMapper signMapper;

    @Resource
    LiveRecordMapper liveRecordMapper;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Resource
    StudentsMapper studentsMapper;

    @Autowired
    ClassesService classesService;

    @Override
    public boolean pushSign2AllStuClients(String stream, Integer limitNumber) {

        //不能在上一次签到的还有效时间内 再次发起签到！
        //live:can:sign:45d048a703d645a1a9264e32c1dff939:*
        Set<String> keys = redisTemplate.keys(
                LiveConst.LIVE_CAN_SIGN_FLAG.KEY() + stream + ":*");
        if (keys.size() > 0) return false;

        String signUuid = UUID.randomUUID().toString().substring(0, 8);

        //在redis中新建一个 live:sign:{stream}:{signUuid} 的set集合。方便保存签到人数。
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(
                LiveConst.LIVE_SIGN.KEY() + stream + ":" + signUuid);
        //TODO 取的时候记得去掉这个对象
        ops.put("0", new SignDetail());


        //限制时间，从现在开始，到 + limitNumber 后结束
        LocalDateTime startTime = LocalDateTime.now();
        LocalDateTime endTime = startTime.plusSeconds(limitNumber.longValue());

        CompletableFuture<Boolean> save2DbTask = CompletableFuture.supplyAsync(() -> {
            return saveTheSignInfo2Db(signUuid, stream, startTime, endTime);
        }, executor);

        try {
            //把这个标志位放在后边，对学生有好处嘿嘿
            //设置一个标志位，标识当前可以签到。过期时间即为签到设置的时长。
            redisTemplate.opsForValue()
                    .set(LiveConst.LIVE_CAN_SIGN_FLAG.KEY() + stream + ":" + signUuid,
                            true,
                            limitNumber,
                            TimeUnit.SECONDS);

            SignInfoVo signInfoVo = new SignInfoVo(signUuid, stream, startTime, endTime);
            LiveLinkEndPoint.broadCast2AllStuInTheLive(
                    R.success()
                            .setCode(ResCodeConst.SIGN.code())
                            .add("signInfo", signInfoVo),
                    stream
            );

            return save2DbTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        return false;
    }

    @Override
    public boolean saveStuSignData(R msg) {
        String stream = (String) msg.getData().get("stream");
        String uuid = (String) msg.getData().get("uuid");
        Integer stuId = (Integer) msg.getData().get("stuId");
        String stuName = (String) msg.getData().get("stuName");
        //没有uuid直接签到失败
        if (uuid == null || stuId == null) return false;

        //这里新加一条学生的签到记录
        //redis中的，我们有前端传来的 stream 和 uuid
        //先判断是否可签到，即
        if (redisTemplate.hasKey(LiveConst.LIVE_CAN_SIGN_FLAG.KEY() + stream + ":" + uuid)) {
            //如果有，那么可以签到。那么将该数据放入到 redis 中对应的 set中， 此时不用考虑并发签到问题。

            //这里有问题，不能保存时间，否则！无法确保 set一致性，因为每次set的时间都是新的，永远不会一样。
            //是否采取 hash？ stuId 为 key， SignDetail 对象为 value， 每次去判断 hash里是否包含 stuId，如果有，不添加，没有则添加。
            //redisTemplate.opsForSet().add(LiveConst.LIVE_SIGN.KEY() + stream + ":" + uuid,
            //        new SignDetail().setSdStuName(stuName).setSdStuId(stuId).setSdSignTime(LocalDateTime.now()));

            //这样，我们放进去的key不会相同，也不必考虑并发性
            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(
                    LiveConst.LIVE_SIGN.KEY() + stream + ":" + uuid);

            //判断是不是已经签到过来，即key是否存在。不存在才put
            if (!ops.hasKey(stuId.toString())) {
                ops.put(stuId.toString(),
                        new SignDetail().setSdStuName(stuName).setSdStuId(stuId).setSdSignTime(LocalDateTime.now()));
            }
            return true;
        }

        //不存在，说明时间已到。此时不允许签到。
        return false;
    }

    @Override
    public R getSignCondition(Integer teaId) {
        //获取当前最新的live recordId
        Integer recordId = (Integer) redisTemplate.opsForHash().get(LiveConst.LIVE_CURRENT_OR_LAST.KEY(), LiveConst.LIVE_RECORD_ID.KEY());
        List<Integer> classIds = (List<Integer>) redisTemplate.opsForHash().get(LiveConst.LIVE_CURRENT_OR_LAST.KEY(), LiveConst.LIVE_CLASS_IDS.KEY());

        if (recordId == null) {
            return R.error().add("error", "暂时没有签到记录哦！ :)");
        }

        HashMap<Integer, Students> stuMap = new HashMap<>();

        //找到该记录的签到记录，也就是找到signUUid.因为数据都没保存到数据库，所以只能找这边的了。。
        List<Sign> signs = signMapper.selectList(new LambdaQueryWrapper<Sign>().eq(Sign::getLiveRecordId, recordId));
        if (signs.size() == 0) return R.error().add("error", "暂时没有签到记录哦！ :)");
        List<SignRecordVo> signRecordVos = new ArrayList<>();
        Integer totalNumber = signs.stream().mapToInt(Sign::getTotalNumber).sum();
        AtomicReference<Integer> realNumber = new AtomicReference<>(0);
        //存放签到情况的map集合 id：签到人数
        HashMap<Integer, Integer> classSignNumberMap = new HashMap<>();

        List<Classes> classes = classesService.listByIds(classIds);

        signs.forEach(sign -> {
            Set<Object> ids
                    = redisTemplate.opsForHash().keys(LiveConst.LIVE_SIGN.KEY() + sign.getLiveUuid() + ":" + sign.getSignUuid());
            int number = ids.size() - 1;
            realNumber.updateAndGet(v -> v + number);

            List<Students> students = studentsMapper.selectBatchId(ids);
            if (students.size() != 0) {
                students.forEach(student -> {
                    if (classSignNumberMap.containsKey(student.getBelongClassId())) {
                        classSignNumberMap.put(student.getBelongClassId(),
                                classSignNumberMap.get(student.getBelongClassId()) + 1);
                    }else {
                        classSignNumberMap.put(student.getBelongClassId(), 1);
                    }
                });

            }

            ArrayList<SignClassInfoVo> signClassInfoVos = new ArrayList<>();
            classes.forEach(c -> {
                SignClassInfoVo signClassInfoVo = new SignClassInfoVo();
                signClassInfoVo.setClassId(c.getClassId()).setClassName(c.getClassRealName())
                        .setSignNumber(classSignNumberMap.get(c.getClassId()));
                signClassInfoVos.add(signClassInfoVo);
            });

            signRecordVos.add(
                    new SignRecordVo().setSign(sign).setRealNumber(number).setSignClassInfos(signClassInfoVos)
            );

        });




        return R.success().add("signRating", (realNumber.get() * 1.0) / totalNumber)
                .add("records", signRecordVos);
    }

    private boolean saveTheSignInfo2Db(String signUuid, String stream,
                                       LocalDateTime startTime, LocalDateTime endTime) {

        //这里添加一条签到记录。
        Sign sign = new Sign();
        sign.setLiveUuid(stream)
                .setSignUuid(signUuid)
                .setCreateTime(startTime)
                .setEndTime(endTime);
        Integer recordId = (Integer) redisTemplate.opsForHash().get(LiveConst.LIVE_START_INFO.KEY() + stream,
                LiveConst.LIVE_RECORD_ID.KEY());

        Integer totalNumber = (Integer) redisTemplate.opsForHash().get(LiveConst.LIVE_START_INFO.KEY() + stream,
                LiveConst.LIVE_TOTAL_NUMBER.KEY());

        if (recordId == null || totalNumber == null) return false;
        sign.setLiveRecordId(recordId).setTotalNumber(totalNumber);
        boolean save = this.save(sign);
        //然后修改 recordId中的签到次数
        boolean update = liveRecordMapper.increaseSignNumber(recordId);

        if (save && update) return true;
        return false;
    }
}
